/*
 * Copyright (c) 2024, Phytium Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <systemd/sd-journal.h>

#include <ipmiblob/crc.hpp>
#include <ipmid/api-types.hpp>
#include <ipmid/api.hpp>
#include <ipmid/handler.hpp>
#include <ipmid/utils.hpp>
#include <phosphor-logging/log.hpp>
#include "tsb.h"

namespace oem
{
constexpr uint8_t cmdTcmReceive = 0x50;
constexpr uint8_t cmdTcmSend = 0x51;
#ifdef TPCM_ENABLE
/* Used by all packages with data. */
struct TcmIpmiHead
{
    uint8_t packageTotal;
    uint8_t packageIndex;
    uint16_t crc16;
} __attribute__((packed));
#endif
} // namespace oem

static TSBLib::TSB tsb;

using namespace phosphor::logging;
#ifdef TPCM_ENABLE
std::array<uint8_t, 4096> resPayload;
uint16_t sentDataSize = 0;
uint32_t resPayloadSize = 0;
#define TPCM_RESULT int
// poweroff
static void powerOff()
{
    const char* chassisStatePath = "/xyz/openbmc_project/state/chassis0";
    const char* chassisStateIntf = "xyz.openbmc_project.State.Chassis";

    try
    {
        sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());

        ipmi::setDbusProperty(
            bus, ipmi::getService(bus, chassisStateIntf, chassisStatePath),
            chassisStatePath, chassisStateIntf, "RequestedPowerTransition",
            "xyz.openbmc_project.State.Chassis.Transition.Off");
    }
    catch (const std::runtime_error& e)
    {
        log<level::ERR>(e.what());
    }
}

ipmi::RspType<std::vector<uint8_t>> ipmiTcmReceiveData(
    const std::vector<uint8_t>& reqData)
{
    static std::vector<uint8_t> reqPayload;
    reqPayload.reserve(256);
    static uint16_t index = 0;
    // Determine if the data is valid
    size_t reqDataSize = reqData.size();

    auto headLen = sizeof(struct oem::TcmIpmiHead);
    if (reqDataSize <= headLen)
    {
        log<level::ERR>("Not a valid data");
        return ipmi::responseInvalidFieldRequest();
    }

    size_t reqBodyLen = reqDataSize - headLen;
    std::vector<uint8_t> bytes(reqBodyLen);

    struct oem::TcmIpmiHead reqHead;
    std::memcpy(&reqHead, reqData.data(), sizeof(reqHead));
    // The first package
    if (!reqHead.packageIndex)
    {
        index = 0;
        reqPayload.clear();
    }

    // The package needs to be sent in order.
    if ((index++) != reqHead.packageIndex)
    {
        index = 0;
        reqPayload.clear();
        log<level::ERR>("Package index error, please resend.");
        return ipmi::responseInvalidFieldRequest();
    }

    // Calculate CRC verification
    std::memcpy(bytes.data(), &reqData[headLen], reqBodyLen);

    if (reqHead.crc16 != ipmiblob::generateCrc(bytes))
    {
        log<level::ERR>("CRC16 verification error.");
        return ipmi::responseInvalidFieldRequest();
    }

    // Copy payload to reqPayload
    reqPayload.insert(reqPayload.end(), bytes.begin(), bytes.end());

    // When receiving the last packet, write the payload of all packets to TCM
    if (reqHead.packageIndex == reqHead.packageTotal - 1)
    {
        TPCM_RESULT result = 0;
        resPayloadSize = resPayload.size();

        result = tsb.ForwardMemory(reqPayload.data(), reqPayload.size(),
                            resPayload.data(), resPayloadSize);
                            
        reqPayload.clear();
        sentDataSize = 0;
    }
    return ipmi::responseSuccess();
}

ipmi::RspType<uint8_t,              // Total number of returned packets
              uint8_t,              // packet index
              uint16_t,             // Payload 16 bit CRC
              std::vector<uint8_t>> // payload data
    ipmiTcmSendData(uint16_t readCount)
{
    if (!resPayloadSize)
    {
        log<level::ERR>("Failed to obtain TPM return");
        return ipmi::responseInvalidFieldRequest();
    }

    auto headLen = sizeof(struct oem::TcmIpmiHead);
    if (readCount <= headLen)
    {
        log<level::ERR>("Request for effective data length too short");
        return ipmi::responseInvalidFieldRequest();
    }

    uint16_t payloadSize = readCount - headLen;
    static uint8_t packageTotal = 0;
    uint8_t packageIndex = sentDataSize / payloadSize;

    // When sending the first data packet, calculate the total number of
    // packets, and the total number of packets remains unchanged for subsequent
    // packets.
    if (packageIndex == 0)
    {
        packageTotal =
            resPayloadSize / payloadSize + !(resPayloadSize % payloadSize == 0);
    }

    // Calculate whether the total amount of packets changes each time, and if
    // there is a change, restart sending.
    uint8_t curPackageTotal =
        resPayloadSize / payloadSize + !(resPayloadSize % payloadSize == 0);
    if (packageTotal != curPackageTotal)
    {
        sentDataSize = 0;
        log<level::ERR>(
            "Request for effective data length change, please resend");
        return ipmi::responseInvalidFieldRequest();
    }

    uint16_t remainDataSize = resPayloadSize - sentDataSize;
    uint16_t resDataSize =
        (remainDataSize > payloadSize) ? payloadSize : remainDataSize;

    std::vector<uint8_t> resData(resDataSize);

    std::memcpy(resData.data(), resPayload.data() + sentDataSize, resDataSize);
    sentDataSize += resDataSize;

    // If all payloads have been sent, reset the total size of the data to be
    // sent and the size of the data already sent.
    if (sentDataSize == resPayloadSize)
    {
        resPayloadSize = 0;
        sentDataSize = 0;
    }

    uint16_t crc16 = ipmiblob::generateCrc(resData);

    return ipmi::responseSuccess(packageTotal, packageIndex, crc16, resData);
}
#else
ipmi::RspType<std::vector<uint8_t>>
    ipmiTcmReceiveData(const std::vector<uint8_t>&)
{
    return ipmi::responseSuccess();
}

ipmi::RspType<std::vector<uint8_t>> // payload data
    ipmiTcmSendData(uint16_t)
{
    return ipmi::responseSuccess();
}
#endif

static void register_fru_functions() __attribute__((constructor));
static void register_fru_functions()
{
    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnOemSix,
                          oem::cmdTcmReceive, ipmi::Privilege::User,
                          ipmiTcmReceiveData);
    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnOemSix, oem::cmdTcmSend,
                          ipmi::Privilege::User, ipmiTcmSendData);
    return;
}
